bboyjing's blog

Maven学习笔记一【Quick Start】

从业开始Maven就一直在用,但没有系统地去学习,这两天搜了一下关于Maven的书籍,比较少而且时间也比较长了。浏览了下官网,有个索引页面写的挺清楚的,所以就将其作为学习教材,采用不完全翻译的方式记录下来。仅作用于个人学习、以及分享,如涉及到版权问题,将立即停止更新。

安装

关于Maven的安装,参照官网。本人当前安装的环境如下:

1
2
3
4
5
6
~❯ mvn --version
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T02:41:47+08:00)
Maven home: /usr/local/apache-maven-3.6.0
Java version: 11.0.1, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home
Default locale: zh_CN_#Hans, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.1", arch: "x86_64", family: "mac"

如上mvn --version是我们使用Maven的第一命令,如要查询所有命令可以通过mvn --help输出。

创建一个项目

在命令行中执行如下命令来创建一个项目:

1
2
3
4
5
6
~❯ mvn archetype:generate \
-DgroupId=com.mycompany.app \
-DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false

成功之后会在当前文件夹生成一个与artifactId同名的目录,即my-app。通常情况下,这一步通过IDEA里提供的可视界面来完成。my-app下的组织结构相信大家都非常清楚了,就没必要解释了,有需要的话可以参照官网标准目录布局的介绍。上述命令执行了一个Maven的行为:archetype:generate,并且将各种参数传递给该行为。前缀archetype是提供行为的插件,上述行为的意思是基于maven-archetype-quickstart原型创建一个简单的项目。其中pom.xml还是有必要来看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<name>my-app</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
...
</project>

下面列出pom.xml每个标签的含义:

  • project 这是pom.xml文件的顶级元素
  • modelVersion 该元素指定POM使用的对象模型的版本,该属性很少更改
  • groupId 该元素指定创建项目的组织的唯一标识符。groupId是项目的关键标识符之一,通常设置成组织的域名
  • artifactId 可以理解为该项目的名称,打包出来的文件会使用到该名称
  • packaging 该项目使用的包类型,不仅仅是打包的类型,如果不写则默认为jar
  • version 指定项目的版本,打包出来的文件会使用到该属性
  • name 项目的显示名称
  • url 指定项目生成的site的url
  • description 项目的描述信息

项目编译

执行如下命令:

1
2
~❯ cd my-app
~/my-app❯ mvn compile

编译后的文件生成在target/classes目录下。

项目打包

执行如下命令:

1
2
~❯ cd my-app
~/my-app❯ mvn package

打包成功后会在target目录下生成jar文件。和上面执行命令mvn archetype:generate不同,该命令表示一个阶段,而不是一个行为目标。阶段是构建生命周期中的一个步骤,是有序序列中的其中一个。当给定一个阶段时,Maven将执行序列中的每个阶段,直到包括定义的阶段为止。例如,如果执行compile阶段,实际执行的阶段将是:

  1. validate
  2. generate-sources
  3. process-sources
  4. generate-resources
  5. process-resources
  6. compile

可以使用如下命令测试生成的jar:

1
2
~/my-app❯ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Hello World!

稍微解释下上面的命令,-cp指定类运行所依赖其他类的路径,com.mycompany.app.App表示要运行的类,将会执行其定义的入口方法main。

运行Maven工具

Maven阶段

如下虽然不是一个全面的列表,但是这些是最常见的执行的默认生命周期阶段。

  • validate:验证项目是否正确,所有的必要信息是否都可用
  • compile:编译项目的源代码
  • test:使用合适的单元测试框架测试编译后的源代码。这些测试不需要被打包或部署
  • package:将编译后的代码以其可分发的格式打包,例如JAR
  • integration-test:如果需要,将包处理并部署到可以运行集成测试的环境中
  • verify:运行任何检查以验证包是否有效并满足质量标准
  • install:将包安装到本地存储库中,作为本地其他项目的依赖项使用
  • deploy:在集成或发布环境中操作,将最终包复制到远程存储库,以便与其他开发人员和项目共享

除了上述默认列表之外,还有两个Maven生命周期值得注意。他们是:

  • clean:清理以前构建的内容
  • site:为该项目生成站点文档

阶段实际上映射到底层行为。每个阶段执行的具体行为取决于项目的打包类型。例如,如果项目类型是jar, package将执行jar:jar;如果项目类型是war,则执行war:war。

有趣的是,阶段和目标可以是按顺序执行的,比如:mvn clean dependency:copy-dependencies package。此命令将清理项目、复制依赖项并打包项目按顺序执行。

生成站点

1
2
~❯ cd my-app
~/my-app❯ mvn site

成功之后将会在target目录下生成site目录,打开index.html可以看到项目的一些信息,比如依赖、使用的插件之类。

SNAPSHOT

看下之前打包的jar文件,其名为my-app-1.0-SNAPSHOT.jar,这里的SNAPSHOT指的是开发分支中的最新代码,不能保证代码是稳定的或不变的。相反release版本是不变的。换句话说,快照版本是最终“发布”版本之前的“开发”版本。快照比它的发布“更早”。

在发布过程中x.y-SNAPSHOT将变成 x.y,同时发布过程还将开发版本增加到x.(y+1)-SNAPSHOT。例如,版本1.0-SNAPSHOT作为版本1.0发布,新的开发版本将变成1.1-SNAPSHOT

使用插件

每当想为Maven项目定制构建时,都可以通过添加或重新配置插件来完成。

下面的例子将配置Java编译器以允许JDK 5.0源代码,将如下代码添加的pom文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
...

我们可以看到Maven中的所有插件看起来都很像一个依赖项——在某些方面确实如此。这个插件将自动下载和使用,如果不指定版本将使用最新的,否则使用指定版本号的插件。

配置元素将给定的参数应用于编译器插件的每个行为。在上面的例子中,编译器插件已经作为构建过程的一部分使用了,这里只是更改了配置。还可以向流程中添加新的行为,并配置特定的行为。这里只是先了解下插件的概念,后面会再深入学习。

JAR中添加资源

另一种不需要对POM进行任何更改就可以满足的常见方式是,将资源打包到JAR文件中。对于这个常见任务,Maven再次依赖于标准目录布局,这意味着通过使用标准的Maven约定,将这些资源放在标准目录结构中来将其打包到jar中。其实也就是resources目录,目前这个目录默认没有创建出来,我们自行创建,然后在其中添加一个名为application.properties的文件。解压下打包的jar文件看看什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
~❯ cd my-app
~/my-app❯ mkdir -p src/main/resources
~/my-app❯ touch src/main/resources/application.properties
~/my-app❯ mvn clean package
~/my-app❯ cd target
~/my-app/target❯ mkdir my-app && tar -zxvf my-app-1.0-SNAPSHOT.jar -C my-app
~/my-app/target❯ cd my-app
~/my-app/target/my-app❯ tree
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│   └── com.mycompany.app
│   └── my-app
│   ├── pom.properties
│   └── pom.xml
├── application.properties
└── com
└── mycompany
└── app
└── App.class

可以看到在解压出来的根目录生成了application.properties文件,同时还有个 META-INF目录,这个是Maven自动生成的。关注下 MANIFEST.MF,该文件定义了与扩展和包相关的数据,其中有个Main-Class属性,配置正确的话,可以打出一个可执行jar包。下面我们通过修改pom.xml的如下插件来实现该功能:

1
2
3
4
5
6
7
8
9
10
11
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.mycompany.app.App</Main-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>

修改完之后来测试下:

1
2
3
4
~❯ cd my-app
~/my-app❯ mvn clean package
~/my-app❯ java -jar target/my-app-1.0-SNAPSHOT.jar
Hello World!

过滤资源文件

有时资源文件需要包含只能在构建时提供的值。要在Maven中实现这一点,可以使用${}语法将包含该值的属性引用放入资源文件。属性可以是pom文件中定义的值、 用户settings.xml中定义的值、外部属性文件中定义的值或系统属性。

直接使用pom文件中的标签属性值

要在复制时使用Maven筛选资源,只需将筛选设置为true,作用于pom.xml中的资源目录:

1
2
3
4
5
6
7
8
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>

下面修改之前添加的application.properties文件,在其中添加获取pom.xml中project的一些信息:

1
2
3
~❯ vim ~/my-app/src/main/resources/application.properties
application.name=${project.name}
application.version=${project.version}

可以执行以下命令进行测试,process-resources是复制和筛选资源的构建生命周期阶段。

1
2
3
4
5
~❯ cd my-app
~/my-app❯ mvn process-resources
~/my-app❯ cat target/classes/application.properties
application.name=my-app
application.version=1.0-SNAPSHOT

引用外部文件的属性值

要引用外部文件中定义的属性,需要在pom.xml中添加对该外部文件的引用。首先,我们创建一个外部属性文件,并将其命名为src/main/filters/filter.properties:

1
2
3
4
~❯ cd my-app
~/my-app❯ mkdir -p src/main/filters
~/my-app❯ vim src/main/filters/filter.properties
my.filter.value=hello!

filters是Maven的目录,作用就是存放资源文件。接下来,我们将在pom.xml中添加对这个新文件的引用:

1
2
3
4
5
<build>
<filters>
<filter>src/main/resources/filter.properties</filter>
</filters>
</build>

最后修改application.properties,添加如下:

1
2
~❯ vim ~/my-app/src/main/resources/application.properties
message=${my.filter.value}

测试下:

1
2
3
4
5
6
~❯ cd my-app
~/my-app❯ mvn process-resources
~/my-app❯ cat target/classes/application.properties
application.name=my-app
application.version=1.0-SNAPSHOT
message=hello!

在pom文件中自定义属性

作为定义my.filter的替代方法,可以直接在pom的properties部分中定义它:

1
2
3
<properties>
<my.properties.value>hello from pom</my.properties.value>
</properties>

修改application.properties,添加如下:

1
2
~❯ vim ~/my-app/src/main/resources/application.properties
msssage2=${my.properties.value}

测试:

1
2
3
4
5
6
7
~❯ cd my-app
~/my-app❯ mvn process-resources
~/my-app❯ cat target/classes/application.properties
application.name=my-app
application.version=1.0-SNAPSHOT
message=hello!
msssage2=hello from pom

获取系统属性

过滤资源还可以从系统属性中获取值,无论是Java的内置属性或者是标准Java -D参数在命令行上定义的属性。修改application.properties文件,添加如下内容:

1
2
3
~❯ vim ~/my-app/src/main/resources/application.properties
java.version=${java.version}
command.line.prop=${command.line.prop}

下面进行测试:

1
2
3
4
5
6
7
8
9
~❯ cd my-app
~/my-app❯ mvn process-resources "-Dcommand.line.prop=hello again"
~/my-app❯ cat target/classes/application.properties
application.name=my-app
application.version=1.0-SNAPSHOT
message=hello!
msssage2=hello from pom
java.version=11.0.1
command.line.prop=hello again

本篇是一个简单的Quick Start,后面将会继续深入。